1. Guarding Against Concurrent Modification

1. Guarding Against Concurrent Modification

1.1. Introduction

In this concrete example we will show why we sometimes need to protect resources from concurrent modification or Race Condition .

The example is an account from which lots of small amounts are retrieved. Here the concurrency is symbolically represented by the different ATM's (Automated Teller Machine), but it could be any financial transaction.

The problem with concurrency issues is that they are non-deterministic. You can only detect them by sheer luck (or misfortune).

In our example we can stack the odds in favor of us detecting the problem, in production these kind of problems are often very hard to reproduce and therefore equally hard to debug and repair.

1.2. A Naive Implementation

In our example there is exactly one Account instance. We first create all the threads - granted, 20001 transactions on a single account might seem unrealistic, but it just serves the purpose of having roughly a 50% chance of an "unwanted" concurrent modification.

After creating the threads, we start them all as fast as possible. Next we have a for loop that calls the join method on each of the threads. This method blocks until the thread is finished and returns immediately if the thread is already done. The word Done therefore gets printed when all the other threads are finished.

Note that the main method runs in its own thread, so there are actually 20002 threads.

package be.ooxs.examples.threads;

import java.util.ArrayList;
import java.util.List;

public class ThreadDemoSynchronized {

	public static void main(String[] args) throws InterruptedException {
		Account account = new Account();
		List<Thread> threads = new ArrayList<Thread>();
		for (int i = 0; i < 20001; i++) {
			threads.add(new ATMThread(account));
		}
		for (Thread thread : threads) {
			thread.start();
		}
		for (Thread thread : threads) {
			thread.join();
		}
		System.out.println("Done");
	}
}

class ATMThread extends Thread {
	Account account;

	ATMThread(Account a) {
		account = a;
	}

	public void run() {
		System.out.println("   Withdrawn: " + account.retrieve(5));
	}
}

class Account {
	int credit = 100000;
	int minimum = 0;

	public void setCredit(int credit) {
		this.credit = credit;
	}

	public void setMinimum(int minimum) {
		this.minimum = minimum;
	}

	public int retrieve(int amount) {
		if (credit - amount >= minimum) {
			credit = credit - amount;
			System.out.println("Account:" + credit);
			return amount;
		} else {
			System.out.println("Account, insufficient credit:" + credit);
			return 0;
		}
	}
}

When we run this code, these are some (handpicked) runs that are relevant to our problem. The outputs differ constantly:

...
   Withdrawn: 5
Account:15820
   Withdrawn: 5
Account:15810
Account:15815
   Withdrawn: 5
   Withdrawn: 5
Account:15805
...
Account:10
   Withdrawn: 5
Account:5
   Withdrawn: 5
Account:0
   Withdrawn: 5
Done

Ok, we just proved that 100 000 - (20 001 ✕ 5) = 0

...
   Withdrawn: 5
Account:0
   Withdrawn: 5
Account, insufficient credit:0
   Withdrawn: 0

Ok, we just proved that 100 000 - (20 000 ✕ 5) = 0. The previous one must have been a bad dream. All is well.

   Withdrawn: 5
Account:125
   Withdrawn: 5
Account:115
Account:120
   Withdrawn: 5
   Withdrawn: 5
Account:110
   Withdrawn: 5
Account:100
Account:105
   Withdrawn: 5
   Withdrawn: 5
Account:95
   Withdrawn: 5
Account:85
Account:90
   Withdrawn: 5
   Withdrawn: 5
Account:80
   Withdrawn: 5
...
Account:20
   Withdrawn: 5
Account:15
   Withdrawn: 5
Done

No, wait, it is 100 000 - (20 001 ✕ 5) = 15.

Depending on the operating system, JVM , hardware etc. results might be different, on my setup the results are correct about half of the time.

What happened is that two threads concurrently - at the same time - are in the retrieve method. One thread check the condition credit - amount >= minimum which is true", uses the value of amount to calculate the new amount e.g. 45 - 5 = 40. Then the other thread reads the amount and calculates the new amount e.g. 45 - 5 = 40. Then the first thread assigns this value (40) to the instance variable then the second thread does exactly the same (40).

Instead of doing 45 - 5 - 5 = 35 we have done 45 - 5 - 5 = 40. In the last output we are left with 15, so that same problem did occur three times.

What we have here is sometimes referred to as a "race condition": multiple threads racing to modify a resource. But frankly, I find that term not very clear.

1.3. A Safe Implementation

A simple modification to the code helps by blocking all but one thread to execute this method at any given time.

	public synchronized int retrieve(int amount) {
	...

The effect is that each time we run the program, the end result is the same and correct:

...
   Withdrawn: 5
Account:0
   Withdrawn: 5
Account, insufficient credit:0
   Withdrawn: 0
Done

In Java this synchronisation can be written differently:

	public int retrieve(int amount) {
		synchronized(this){
			...
		}
	}

Threads compete for a Mutex or Monitor , that is, an exclusive right to a particular object. If a thread obtains the exclusive right to that object, that thread can enter the block (or method). This guarantees that only one thread at a time can execute any code in the block. The other that want to access the guarded resource threads are in the Blocking state.